
package cn.uncode.rpc.spring;

import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_BASE_CALLER;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_BASE_PROVIDER;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_CLASS;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_EXT_CONFIG;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_ID;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_METHODS;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_METHOD_SET;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_NAME;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_PROTOCOL;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_PROTOCOLS;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_REF;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_REGISTRY;
import static cn.uncode.rpc.spring.SpringSupportConstant.BEAN_DEFINITION_PARSER_SLIPT;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import cn.uncode.rpc.common.CommonConstant;
import cn.uncode.rpc.config.MethodConfig;
import cn.uncode.rpc.exception.SpringSupportException;




public class UncodeBeanDefinitionParser  implements BeanDefinitionParser {//extends AbstractSingleBeanDefinitionParser

    private final Class<?> beanClass;

    private final boolean required;

    public UncodeBeanDefinitionParser(Class<?> beanClass, boolean required) {
        this.beanClass = beanClass;
        this.required = required;
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        try {
            return parse(element, parserContext, beanClass, required);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) throws ClassNotFoundException{
    	RootBeanDefinition bd = new RootBeanDefinition();
    	bd.setBeanClass(beanClass);
    	// 不允许lazy init
    	bd.setLazyInit(false);
    	
    	// 如果没有id则按照规则生成一个id,注册id到context中
        String id = element.getAttribute(BEAN_DEFINITION_PARSER_ID);
        if(StringUtils.isBlank(id) && required){
        	String generatedBeanName = element.getAttribute(BEAN_DEFINITION_PARSER_NAME);
        	if(StringUtils.isBlank(generatedBeanName)){
        		generatedBeanName = element.getAttribute(BEAN_DEFINITION_PARSER_CLASS);
        	}
        	if(StringUtils.isBlank(generatedBeanName)){
        		generatedBeanName = beanClass.getName();
        	}
        	id = generatedBeanName;
        	int counter = 2;
        	while(parserContext.getRegistry().containsBeanDefinition(id)){
        		id = generatedBeanName + (counter++);
        	}
        }
        
        if(StringUtils.isNotBlank(id)){
        	if (parserContext.getRegistry().containsBeanDefinition(id)) {
                throw new SpringSupportException("Duplicate spring bean id " + id);
            }
            parserContext.getRegistry().registerBeanDefinition(id, bd);
        }
        bd.getPropertyValues().addPropertyValue(BEAN_DEFINITION_PARSER_ID, id);
        if (ProtocolConfigBean.class.equals(beanClass)) {
            UncodeNamespaceHandler.protocolDefineNames.add(id);
        } else if (RegistryConfigBean.class.equals(beanClass)) {
        	UncodeNamespaceHandler.registryDefineNames.add(id);
        } else if (BasicProviderConfigBean.class.equals(beanClass)) {
        	UncodeNamespaceHandler.basicProviderConfigDefineNames.add(id);
        } else if (BasicCallerConfigBean.class.equals(beanClass)) {
        	UncodeNamespaceHandler.basicCallerConfigDefineNames.add(id);
        } else if (ProviderConfigBean.class.equals(beanClass)) {
            String className = element.getAttribute("class");
            if (className != null && className.length() > 0) {
                RootBeanDefinition classDefinition = new RootBeanDefinition();
                classDefinition.setBeanClass(Class.forName(className, true, Thread.currentThread().getContextClassLoader()));
                classDefinition.setLazyInit(false);
                parseProperties(element.getChildNodes(), classDefinition);
                bd.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + CommonConstant.THE_IMPLEMENTATION_CLASS_DEFAULT_SUFFIX));
            }
        }
        
        Set<String> props = new HashSet<String>();
        ManagedMap<String, TypedStringValue> parameters = null;
        // 把配置文件中的可以set的属性放到bd中
        for(Method setter : beanClass.getMethods()){
        	String name = setter.getName();
        	// 必须是setXXX
        	if(name.length() <= 3 || !name.startsWith(BEAN_DEFINITION_PARSER_METHOD_SET) || !Modifier.isPublic(setter.getModifiers()) || setter.getParameterTypes().length != 1){
        		continue;
        	}
        	String property = (name.substring(3, 4).toLowerCase() + name.substring(4)).replaceAll("_", "-");
        	props.add(property);
        	if(BEAN_DEFINITION_PARSER_ID.equals(property)){
        		bd.getPropertyValues().addPropertyValue(BEAN_DEFINITION_PARSER_ID, id);
        		continue;
        	}
        	String value = element.getAttribute(property);
        	if(BEAN_DEFINITION_PARSER_METHODS.equals(property)){
        		parseMethods(id, element.getChildNodes(), bd, parserContext);
        	}
        	if(StringUtils.isBlank(value)){
        		continue;
        	}
        	value = value.trim();
        	if(value.length() == 0){
        		continue;
        	}
        	Object reference;
        	if(BEAN_DEFINITION_PARSER_REF.equals(property)){
        		if(parserContext.getRegistry().containsBeanDefinition(value)){
        			BeanDefinition refBean  = parserContext.getRegistry().getBeanDefinition(value);
        			if(!refBean.isSingleton()){
        				throw new SpringSupportException("The exported service ref " + value + " must be singleton! Please set the " + value
                                + " bean scope to singleton, eg: <bean id=\"" + value + "\" scope=\"singleton\" ...>");
        			}
        		}
        		reference = new RuntimeBeanReference(value);
        	}else if(BEAN_DEFINITION_PARSER_PROTOCOL.equals(property) && StringUtils.isNotBlank(value)){
        		if(!value.contains(",")){
        			reference = new RuntimeBeanReference(value);
        		}else{
        			parseMultiRef(BEAN_DEFINITION_PARSER_PROTOCOLS, value, bd, parserContext);
        			reference = null;
        		}
        	}else if(BEAN_DEFINITION_PARSER_REGISTRY.equals(property)){
        		parseMultiRef("registries", value, bd, parserContext);
        		reference = null;
        	}else if (BEAN_DEFINITION_PARSER_BASE_PROVIDER.equals(property)) {
                reference = new RuntimeBeanReference(value);
            } else if (BEAN_DEFINITION_PARSER_BASE_CALLER.equals(property)) {
                reference = new RuntimeBeanReference(value);
            } else if (BEAN_DEFINITION_PARSER_EXT_CONFIG.equals(property)) {
                reference = new RuntimeBeanReference(value);
            }else {
                reference = new TypedStringValue(value);
            }
        	if(reference  != null){
        		bd.getPropertyValues().addPropertyValue(property, reference);
        	}
        }
        if (ProtocolConfigBean.class.equals(beanClass)) {
            // 把剩余的属性放到protocol的parameters里面
            NamedNodeMap attributes = element.getAttributes();
            int len = attributes.getLength();
            for (int i = 0; i < len; i++) {
                Node node = attributes.item(i);
                String name = node.getLocalName();
                if (!props.contains(name)) {
                    if (parameters == null) {
                        parameters = new ManagedMap<String, TypedStringValue>();
                    }
                    String value = node.getNodeValue();
                    parameters.put(name, new TypedStringValue(value, String.class));
                }
            }
            bd.getPropertyValues().addPropertyValue("parameters", parameters);
        }
        return bd;
    }
    
    
    private static void parseProperties(NodeList nodeList, RootBeanDefinition beanDefinition) {
        if (nodeList != null && nodeList.getLength() > 0) {
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node instanceof Element) {
                    if ("property".equals(node.getNodeName()) || "property".equals(node.getLocalName())) {
                        String name = ((Element) node).getAttribute("name");
                        if (name != null && name.length() > 0) {
                            String value = ((Element) node).getAttribute("value");
                            String ref = ((Element) node).getAttribute("ref");
                            if (value != null && value.length() > 0) {
                                beanDefinition.getPropertyValues().addPropertyValue(name, value);
                            } else if (ref != null && ref.length() > 0) {
                                beanDefinition.getPropertyValues().addPropertyValue(name, new RuntimeBeanReference(ref));
                            } else {
                                throw new UnsupportedOperationException("Unsupported <property name=\"" + name
                                        + "\"> sub tag, Only supported <property name=\"" + name + "\" ref=\"...\" /> or <property name=\""
                                        + name + "\" value=\"...\" />");
                            }
                        }
                    }
                }
            }
        }
    }
    
    
    private static void parseMultiRef(String property, String value, RootBeanDefinition beanDefinition, ParserContext parserContext) {
        String[] values = value.split(BEAN_DEFINITION_PARSER_SLIPT);
        ManagedList<RuntimeBeanReference> list = null;
        for(int i = 0; i < values.length; i++){
        	String v = values[i];
        	if(StringUtils.isNotBlank(v)){
        		if(list == null){
        			list = new ManagedList<RuntimeBeanReference>();
        		}
        		list.add(new RuntimeBeanReference(v));
        	}
        }
        beanDefinition.getPropertyValues().addPropertyValue(property, list);
    }
    
    
    private static void parseMethods(String id, NodeList nodeList, RootBeanDefinition beanDefinition, ParserContext parserContext)
            throws ClassNotFoundException {
        if (nodeList != null && nodeList.getLength() > 0) {
            ManagedList<BeanDefinitionHolder> methods = null;
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node instanceof Element) {
                    Element element = (Element) node;
                    if ("method".equals(node.getNodeName()) || "method".equals(node.getLocalName())) {
                        String methodName = element.getAttribute("name");
                        if (methodName == null || methodName.length() == 0) {
                            throw new IllegalStateException("<motan:method> name attribute == null");
                        }
                        if (methods == null) {
                            methods = new ManagedList<BeanDefinitionHolder>();
                        }
                        BeanDefinition methodBeanDefinition = parse((Element) node, parserContext, MethodConfig.class, false);
                        String name = id + "." + methodName;
                        BeanDefinitionHolder methodBeanDefinitionHolder = new BeanDefinitionHolder(methodBeanDefinition, name);
                        methods.add(methodBeanDefinitionHolder);
                    }
                }
            }
            if (methods != null) {
                beanDefinition.getPropertyValues().addPropertyValue("methods", methods);
            }
        }
    }

}
